package com.atguigu.gmall.common.config.mq;

import com.alibaba.fastjson.JSON;
import com.atguigu.gmall.common.util.MD5;
import com.rabbitmq.client.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.core.Message;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.retry.support.RetryTemplate;
import org.springframework.stereotype.Service;

import java.io.IOException;

//发送消息
@Slf4j
@Service
public class MqService {

    @Autowired
    private RabbitTemplate rabbitTemplate;
    @Autowired
    StringRedisTemplate redisTemplate;

    //无论发送是否成功，confirm回调都会被触发，如果消息不能抵达queue，return回调会触发

    //springboot默认，一个组件MqService 只有一个有参构造器，有参构造器里的参数 自动从容器中拿，
    //springboot启动后，自动扫描组件MqService（因为@Service），创建组件对象，springboot默认把配好的rabbitTemplate交给我们，我们把它保存起来
    //this.rabbitTemplate = rabbitTemplate;
    public MqService(RabbitTemplate rabbitTemplate,StringRedisTemplate redisTemplate){
        this.rabbitTemplate = rabbitTemplate;
        this.redisTemplate = redisTemplate;
        initTemplate();
    }

    //发送消息
    public void send(Object message, String exchange,String routingKey){
        rabbitTemplate.convertAndSend(exchange,routingKey, JSON.toJSONString(message));
    }

    private void initTemplate() {
        //发送成功 ack:true
        //发送失败
        //      交换机不存在 ack:false
        //发送失败
        //      队列不存在 ack:true
        this.rabbitTemplate.setConfirmCallback((CorrelationData correlationData,//关联的数据
                                           boolean ack,//回复状态
                                           String cause)->{//原因
            log.info("confirm回调：correlationData:{},ack:{},cause:{}",correlationData,ack,cause);
            //发送不成功的消息记录到后台数据库，等待人工处理
        });

        //成功：不触发
        //失败：触发 队列不存在
        this.rabbitTemplate.setReturnCallback((Message message,//当前消息
                                          int replyCode,//回复码
                                          String replyText,//文本
                                          String exchange,//交换机
                                          String routingKey)->{//路由键
            log.info("return回调：message:{},replyCode:{},replyText:{},exchange:{},routingKey:{}",
                    message,replyCode,replyText,exchange,routingKey);
            //发送不成功的消息记录到后台数据库，等待人工处理
        });

        //设置重试模板
        rabbitTemplate.setRetryTemplate(new RetryTemplate());
    }

    //消费失败，重试5次重新入队
    public void retry(Channel channel, long deliveryTag, String content, Integer retryCount) throws IOException {
        //消息通过md5算法，判断只要消息的MD5相同，就是同一个消息
        String md5 = MD5.encrypt(content);
        //同一个消息重试5次  在redis里进行计数
        Long increment = redisTemplate.opsForValue().increment("msg:count:" + md5);
        if (increment <= retryCount){
            log.info("消费失败，重新入队，次数：{}",increment);
            //回复不OK，继续重试
            channel.basicNack(deliveryTag,false,true);;// true消息重新入队
        } else {
            log.error("消费重试超过最大限定次数，已经达到{}，记录至数据库，不再重试，等待人工处理",increment);
            //删除redis的计数
            redisTemplate.delete("msg:count:" + md5);
            //回复OK，不重试了
            channel.basicAck(deliveryTag, false);
        }
    }
}
